﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Diagnostics;

namespace CS544_Protocol_Client
{

    public class ClientEventArgs : EventArgs
    {
        public readonly Message ServerMessage; //raw message from server
        public readonly Error Error; //specific error
      
        public ClientEventArgs(Message msg)
        {
            this.ServerMessage = msg;
        }

        public ClientEventArgs(Message msg, Error err)
            : this(msg)
        {
            this.Error = err;
        }
                       
    }

    public delegate void ClientEventHandler(object sender, ClientEventArgs e);

    public class ClientControl : IDisposable
    {
        public event ClientEventHandler LoginResult;
        public event ClientEventHandler LogoutResult;
        public event ClientEventHandler RegisterResult;
        public event ClientEventHandler MessageOutResult;
        public event ClientEventHandler StatusResult;
        public event ClientEventHandler ListUsersResult;
        public event ClientEventHandler NewPrivateMessage;
        public event ClientEventHandler UserAway;
        public event ClientEventHandler UserOnline;
        public event EventHandler NewChatRoomMessage;
        public event EventHandler Registering;
        public event EventHandler UserLogin;
        public event EventHandler UserLogout;
        public event EventHandler UserListUpdate;
        

        private ChatClient myChatClient = new ChatClient();// the main chat client object;
        private PrivateMessageManager myPrivateMessageManager = new PrivateMessageManager(); //holds private messages, deals with them.
        private ChatRoom myChatRoom = new ChatRoom("Public Room"); //holds chat room userlist and message log
        private List<Message> myMessageQueue = new List<Message>();
        public User currentUser { get; private set; }//hold user info
        private DFA protocolDFA = new DFA();//deals with protocol state for client.

        //Constructor
        public ClientControl()
        {
            myChatClient.MessageReceived += new MessageReceivedHandler(myChatClient_MessageReceived);
            this.LoginResult += new ClientEventHandler(ClientControl_LoginResult);
            this.RegisterResult += new ClientEventHandler(ClientControl_RegisterResult);
            this.myChatRoom.UserUpdate += new EventHandler(myChatRoom_UserUpdate);
            this.myChatRoom.MessageUpdate += new EventHandler(myChatRoom_MessageUpdate);
        }


        //Handle when chat room has new message
        void myChatRoom_MessageUpdate(object sender, EventArgs e)
        {
            OnNewChatRoomMessage();
        }


       //Handle when user list event occurs in chatroom
        void myChatRoom_UserUpdate(object sender, EventArgs e)
        {
            OnUserListUpdate();            
        }


        //Handle when register result is recieved for DFA state change
        void ClientControl_RegisterResult(object sender, ClientEventArgs e)
        {
            this.protocolDFA.processMessage(e.ServerMessage);
        }


        //On login result process message for DFA state change
        void ClientControl_LoginResult(object sender, ClientEventArgs e)
        {

            this.protocolDFA.processMessage(e.ServerMessage);
            this.getUserList();
        }        
 


        //handle events raised by the chat client
        void myChatClient_MessageReceived(object sender, MessageEventArgs e)
        {
            Message serverMessage = e.Message;
            State state = this.protocolDFA.ProtocolState;
            #region OPEN
            //if the protocol state is open then allow the following messages to be processed.
            if (this.protocolDFA.ProtocolState == State.OPEN)
            {
                switch (serverMessage.Type)
                {                    

                    case MessageType.MESSAGE_IN:
                        {
                            MessageInBody body = (MessageInBody)serverMessage.Body;

                            //If message is private
                            if (body.Action == "PRIVATE_MESSAGE")
                            {

                                OnNewPrivateMessage(new ClientEventArgs(serverMessage));

                            }

                            //If message is public
                            if (body.Action == "PUBLIC_MESSAGE")
                            {
                                myChatRoom.addNewMessage(body.Sender, body.Message);
                            }



                            //If a logged in user changes status
                            if (body.Action == "STATUS_UPDATE")
                            {

                                if(body.Message.Contains("Away"))
                                {
                                    this.myPrivateMessageManager.addToAwayList(body.Sender, body.Message);
                                    User user = new User();
                                    user.setUsername(body.Sender);
                                    user.setStatus("Away");
                                    this.myChatRoom.changeStatus(user);                                  

                                    OnUserAway(new ClientEventArgs(serverMessage));
                                }

                                if(body.Message.Contains("Online"))
                                {
                                    this.myPrivateMessageManager.removeFromAwayList(body.Sender);
                                    User user = new User();
                                    user.setUsername(body.Sender);
                                    user.setStatus("");
                                    this.myChatRoom.changeStatus(user);    

                                    OnUserOnline(new ClientEventArgs(serverMessage));
                                }

                            }


                            //If a user logs into the chat room
                            if (body.Action == "LOGIN_UPDATE")
                            {
                                this.getUserList();
                                OnUserLogin();                              
                                
                            }


                            //If a user logs out of the chat room
                            if (body.Action == "LOGOUT_UPDATE")
                            {
                                this.getUserList();
                                OnUserLogout();
                            }

                            break;
                        }
                    case MessageType.MESSAGE_OUT_ACK:
                        {
                            OnMessageOutResult(new ClientEventArgs(serverMessage));

                            //remove the outgoing message from the queue.
                            MessageOutAckBody body = (MessageOutAckBody)serverMessage.Body;
                            Message msg = myMessageQueue.Where(m => ((MessageOutBody)m.Body).MessageID == body.MessageID).FirstOrDefault();
                            myMessageQueue.Remove(msg);
                            break;
                        }
                    case MessageType.MESSAGE_OUT_NAK:
                        {
                            MessageOutNakBody body = (MessageOutNakBody)serverMessage.Body;
                            OnMessageOutResult(new ClientEventArgs(serverMessage, new Error(body.ErrorCode, body.Description)));

                            //re-send the message
                            Message msg = myMessageQueue.Where(m=>((MessageOutBody)m.Body).MessageID == body.MessageID).First();
                            //myChatClient.SendMessage(msg);
                            break;
                        }
                    case MessageType.SET_STATUS_ACK:
                        {
                            OnStatusResult(new ClientEventArgs(serverMessage));

                            //remove from the queue
                            Message msg = myMessageQueue.Where(m => m.Type == MessageType.SET_STATUS).First();
                            myMessageQueue.Remove(msg);
                            break;
                        }
                    case MessageType.SET_STATUS_NAK:
                        {
                            NakBody body = (NakBody)serverMessage.Body;
                            OnStatusResult(new ClientEventArgs(serverMessage, new Error(body.ErrorCode, body.Description)));

                            //re-send the message
                            Message msg = myMessageQueue.Where(m => m.Type == MessageType.SET_STATUS).First();
                            //myChatClient.SendMessage(msg);
                            break;
                        }
                    case MessageType.LIST_USERS_ACK:
                        {
                            
                            ListUsersAckBody body = (ListUsersAckBody)serverMessage.Body;
                           // Dictionary<int, string> uList = new Dictionary<int, string>();
                            List<User> uList = new List<User>();
                            for (int i = 1; i <= body.Users.Length; i++)
                            {
                                User user = new User();
                                user.setUsername(body.Users[i - 1]);
                                uList.Add(user);
                                
                            }
                            myChatRoom.updateUserList(uList);

                            OnListUsersResult(new ClientEventArgs(serverMessage));

                            //remove the message from the queue
                            Message msg = myMessageQueue.Where(m => m.Type == MessageType.LIST_USERS).First();
                            myMessageQueue.Remove(msg);
                            break;
                        }
                    case MessageType.LIST_USERS_NAK:
                        {
                            NakBody body = (NakBody)serverMessage.Body;
                            OnListUsersResult(new ClientEventArgs(serverMessage, new Error(body.ErrorCode, body.Description)));

                            //re-send the message
                            Message msg = myMessageQueue.Where(m => m.Type == MessageType.LIST_USERS).First();
                            //myChatClient.SendMessage(msg);
                            break;
                        }
                }
            }
            #endregion

            #region REGISTERING or CLOSED
            //if the protocol state is registering then allow the following messages to be processed.
            if (this.protocolDFA.ProtocolState == State.REGISTERING || this.protocolDFA.ProtocolState == State.CLOSED)
            {
                switch (serverMessage.Type)
                { 

                    case MessageType.REGISTER_ACK:
                        {
                            OnRegisterResult(new ClientEventArgs(serverMessage));

                            //remove from the queue
                            UserAckBody body = (UserAckBody)serverMessage.Body;
                            Message msg = myMessageQueue.Where(m => m.Type == MessageType.REGISTER && ((RegisterBody)m.Body).Username==body.Username).First();
                            myMessageQueue.Remove(msg);
                            break;
                        }
                    case MessageType.REGISTER_NAK:
                        {
                            UserNakBody body = (UserNakBody)serverMessage.Body;
                            OnRegisterResult(new ClientEventArgs(serverMessage, new Error(body.ErrorCode, body.Description)));
                        
                            //re-send the message
                            //Message msg = myMessageQueue.Where(m => m.Type == MessageType.REGISTER && ((RegisterBody)m.Body).Username==body.Username).First();
                            //myChatClient.SendMessage(msg);
                            break;
                        }
                }
            }
            #endregion

            #region AUTHENTICATING
            //if the protocol state is authenticating then allow the following messages to be processed.
            if (this.protocolDFA.ProtocolState == State.AUTHENTICATING)
            {
                switch (serverMessage.Type)
                {
                    case MessageType.LOGIN_ACK:
                        {
                            

                            //remove from the queue
                            UserAckBody body = (UserAckBody)serverMessage.Body;
                            Message msg = myMessageQueue.Where(m => m.Type == MessageType.LOGIN && ((LoginBody)m.Body).Username == body.Username).First();
                            myMessageQueue.Remove(msg);

                            OnLoginResult(new ClientEventArgs(serverMessage));

                            break;
                        }
                    case MessageType.LOGIN_NAK:
                        {
                            UserNakBody body = (UserNakBody)serverMessage.Body;
                            OnLoginResult(new ClientEventArgs(serverMessage, new Error(body.ErrorCode, body.Description)));

                            //re-send the message
                            //Message msg = myMessageQueue.Where(m => m.Type == MessageType.LOGIN && ((LoginBody)m.Body).Username == body.Username).First();
                            //myChatClient.SendMessage(msg);
                            break;
                        }
                }
            }
            #endregion

            #region CLOSING

            if (this.protocolDFA.ProtocolState == State.CLOSING)
            {
                switch (serverMessage.Type)
                {
                    case MessageType.LOGOUT_ACK:
                        {
                            OnLogoutResult(new ClientEventArgs(serverMessage));

                            //remove the message from the queue
                            Message msg = myMessageQueue.Where(m => m.Type == MessageType.LOGOUT).First();
                            myMessageQueue.Remove(msg);
                            break;
                        }
                    case MessageType.LOGOUT_NAK:
                        {
                            NakBody body = (NakBody)serverMessage.Body;
                            OnLogoutResult(new ClientEventArgs(serverMessage, new Error(body.ErrorCode, body.Description)));

                            //re-send the message
                            Message msg = myMessageQueue.Where(m => m.Type == MessageType.LOGOUT).First();
                            //myChatClient.SendMessage(msg);
                            break;
                        }
                }
            }

            #endregion

            //process server message to set state
            this.protocolDFA.processMessage(serverMessage);
        }



        //Initiate user registration
        public void register(string username, string password)
        {

            try
            {
                myChatClient.Connect();
                string newUsername = username;
                string newPassword = password;

                User newUser = new User();
                this.currentUser = newUser;
                this.currentUser.setUsername(newUsername);
                this.currentUser.setPassword(newPassword);

                RegisterBody body = new RegisterBody();
                body.Username = username;
                body.Password = password;

                Message msg = new Message();
                msg.Type = MessageType.REGISTER;
                msg.Body = body;

                myMessageQueue.Add(msg);

                this.protocolDFA.processMessage(msg);
                OnRegistering();
                myChatClient.SendMessage(msg);
            }
            catch (Exception ex)
            {
                throw new Exception("An error occurred while trying to register", ex);
            }

             
        }//end register()

        //Initiate user login
        public void login(string username, string password)
        {

            try
            {
                if (this.protocolDFA.ProtocolState == State.CLOSED)
                {
                    this.myChatClient.Connect();
                }
                string newUsername = username;
                string newPassword = password;

                User newUser = new User();
                this.currentUser = newUser;
                this.currentUser.setUsername(newUsername);
                this.currentUser.setPassword(newPassword);

                LoginBody body = new LoginBody();
                body.Username = username;
                body.Password = password;
                body.Version = "1";

                Message msg = new Message();
                msg.Type = MessageType.LOGIN;
                msg.Body = body;

                myMessageQueue.Add(msg);

                this.protocolDFA.processMessage(msg);
                myChatClient.SendMessage(msg);
            }
            catch (Exception ex)
            {
                throw new Exception("An error occurred while trying to login", ex);
            }

        }//end login()

        public void logout()
        {
            //we should only logout if the current protocol state is
            //OPEN
            if (protocolDFA.ProtocolState == State.OPEN)
            {
                //register for our own logout result event
                //so that we can gracefully disconnect
                this.LogoutResult += new ClientEventHandler(ClientControl_LogoutResult);

                Message msg = new Message();
                msg.Type = MessageType.LOGOUT;

                myMessageQueue.Add(msg);

                myChatClient.SendMessage(msg);
                this.protocolDFA.processMessage(msg);
            }
        }

        void ClientControl_LogoutResult(object sender, ClientEventArgs e)
        {
            if (e.Error == null)
                this.disconnect();
        }

        //Send message to room
        public void sendRoomMessage(string message)
        {
                        
            //If the user is logged in and the protocol state is open then send a message
            if (this.protocolDFA.ProtocolState == State.OPEN)
            {
                MessageOutBody body = new MessageOutBody();
                body.MessageID = myChatClient.MessageID;
                body.Message = message;

                Message msg = new Message();
                msg.Type = MessageType.MESSAGE_OUT;
                msg.Body = body;

                myMessageQueue.Add(msg);

                myChatClient.SendMessage(msg);
                myChatRoom.addNewMessage(this.currentUser.Username, message);
            }

        }//end sendRoomMessage()

        //Send private message
        public void sendPrivateMessage(string message, string recipient)
        {

            //If the user is logged in and the protocol state is open then send a message
            if (this.protocolDFA.ProtocolState == State.OPEN)
            {
                MessageOutBody body = new MessageOutBody();
                body.MessageID = myChatClient.MessageID;
                body.Message = message;
                body.Recipient = recipient;

                Message msg = new Message();
                msg.Type = MessageType.MESSAGE_OUT;
                msg.Body = body;

                myMessageQueue.Add(msg);

                myChatClient.SendMessage(msg);
                myPrivateMessageManager.addNewMessage(this.currentUser.Username, message);
            }

        }//end sendPrivateMessage()

        public void setStatus(string status, string description)
        {
            
            //If the user is logged in and the protocol state is open then set status
            if (this.protocolDFA.ProtocolState == State.OPEN)
            {
                if (status != "Online")
                {
                    this.currentUser.setStatus(status);
                }
                else if (status == "Online")
                {
                    this.currentUser.setStatus("");
                }
                            

                StatusBody body = new StatusBody();
                body.Status = status;
                body.Description = description;

                Message msg = new Message();
                msg.Type = MessageType.SET_STATUS;
                msg.Body = body;

                myMessageQueue.Add(msg);

                myChatClient.SendMessage(msg);
                myChatRoom.changeStatus(this.currentUser);
            }
        }


        //Removes private message from PM manager
        public void removePrivateMessage(string ofSender)
        {
            myPrivateMessageManager.removeMessage(ofSender);
        }

        //Get current user list
        public void getUserList()
        {
            
            //If the user is logged in and the protocol state is open then get user list
            if (this.protocolDFA.ProtocolState == State.OPEN)
            {
                Message msg = new Message();
                msg.Type = MessageType.LIST_USERS;

                myMessageQueue.Add(msg);

                myChatClient.SendMessage(msg);
              
            }
            

        }//end getUserList()


        //Get current user list from the chatroom for display
        public List<User> getUserListForDisplay()
        {

            return this.myChatRoom.UserList;
        }


        //Get list of away user
        public List<User> getAwayList()
        {

            return this.myPrivateMessageManager.AwayList;
        }
                

        //Get most recently added message in the chat room
        public string getNewRoomMessage()
        {

            List<string> roomMessages = myChatRoom.ChatLog;           
            string recentMessage = roomMessages[roomMessages.Count - 1];

            return recentMessage;

        }//end getNewRoomMessage()


        //Get list of updated private messages
        public Dictionary<string, string> getUpdatedPrivateMessages()
        {

            return this.myPrivateMessageManager.getUpdatedMessages();
        }


        //Disconnect chat client
        public void disconnect()
        {
            this.myChatClient.Disconnect();
          
        }

        


        private void OnLoginResult(ClientEventArgs args)
        {
            if (LoginResult != null)
                LoginResult(this, args);
        }

        private void OnLogoutResult(ClientEventArgs args)
        {
            if (LogoutResult != null)
                LogoutResult(this, args);
        }

        private void OnRegisterResult(ClientEventArgs args)
        {
            if (RegisterResult != null)
                RegisterResult(this, args);
        }

        private void OnMessageOutResult(ClientEventArgs args)
        {
            if (MessageOutResult != null)
                MessageOutResult(this, args);
        }

        private void OnStatusResult(ClientEventArgs args)
        {
            if (StatusResult != null)
                StatusResult(this, args);
        }

        private void OnListUsersResult(ClientEventArgs args)
        {
            if (ListUsersResult != null)
                ListUsersResult(this, args);
        }

        private void OnNewPrivateMessage(ClientEventArgs args)
        {
            if (NewPrivateMessage != null)
                NewPrivateMessage(null, args);
        }

        private void OnUserAway(ClientEventArgs args)
        {
            if (UserAway != null)
                UserAway(null, args);
        }

        private void OnUserOnline(ClientEventArgs args)
        {
            if (UserOnline != null)
                UserOnline(null, args);
        }
        
        private void OnNewChatRoomMessage()
        {
            if (NewChatRoomMessage != null)
                NewChatRoomMessage(null, new EventArgs());
        }

        private void OnRegistering()
        {
            if (Registering != null)
                Registering(this, new EventArgs());
        }


        private void OnUserLogin()
        {
            if (UserLogin != null)
                Registering(this, new EventArgs());
        }


        private void OnUserLogout()
        {
            if (UserLogout != null)
                UserLogout(this, new EventArgs());
        }


        private void OnUserListUpdate()
        {
            if (UserListUpdate != null)
                UserListUpdate(this, new EventArgs());
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (myChatClient != null)
                myChatClient.Disconnect();

        }

        #endregion

    }
}
